#define _CRT_SECURE_NO_WARNINGS 1

#include "Chip8.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

unsigned char fontset[80] =
{
	0xF0, 0x90, 0x90, 0x90, 0xF0, //0
	0x20, 0x60, 0x20, 0x20, 0x70, //1
	0xF0, 0x10, 0xF0, 0x80, 0xF0, //2
	0xF0, 0x10, 0xF0, 0x10, 0xF0, //3
	0x90, 0x90, 0xF0, 0x10, 0x10, //4
	0xF0, 0x80, 0xF0, 0x10, 0xF0, //5
	0xF0, 0x80, 0xF0, 0x90, 0xF0, //6
	0xF0, 0x10, 0x20, 0x40, 0x40, //7
	0xF0, 0x90, 0xF0, 0x90, 0xF0, //8
	0xF0, 0x90, 0xF0, 0x10, 0xF0, //9
	0xF0, 0x90, 0xF0, 0x90, 0x90, //A
	0xE0, 0x90, 0xE0, 0x90, 0xE0, //B
	0xF0, 0x80, 0x80, 0x80, 0xF0, //C
	0xE0, 0x90, 0x90, 0x90, 0xE0, //D
	0xF0, 0x80, 0xF0, 0x80, 0xF0, //E
	0xF0, 0x80, 0xF0, 0x80, 0x80  //F
};

void Chip8::init()
{
	pc = 0x200;
	opcode = 0;
	I = 0;
	sp = 0;
	delay_timer = 0;
	sound_timer = 0;
	drawFlag = true;

	memset(memory, 0, sizeof(memory));
	memset(V, 0, sizeof(V));
	memset(gfx, 0, sizeof(gfx));
	memset(stack, 0, sizeof(stack));
	memset(key, 0, sizeof(key));

	for(int i = 0; i < 80; ++i) {
		memory[i] = fontset[i];
	}
	srand((unsigned int)time(NULL));
}

bool Chip8::loadApplication(const char* fileName)
{
	init();
	FILE* file = fopen(fileName, "rb");
	fseek(file, 0, SEEK_END);
	int fileSize = ftell(file);
	rewind(file);
	char* buffer = (char*)malloc(sizeof(char) * fileSize);
	fread(buffer, sizeof(char), fileSize, file);
	for(int i = 0; i < fileSize; ++i) {
		memory[512+i] = buffer[i];
	}
	fclose(file);
	free(buffer);
	return true;
}

void Chip8::emulateCycle()
{
	opcode = memory[pc] << 8 | memory[pc+1];
	switch(opcode & 0xF000) {
	case 0x0000:
		switch(opcode & 0x000F) {
		case 0x0000:
			op_00E0(); break;
		case 0x000E:
			op_00EE(); break;
		}
		break;
	case 0x1000:
		op_1NNN(); break;
	case 0x2000:
		op_2NNN(); break;
	case 0x3000:
		op_3XNN(); break;
	case 0x4000:
		op_4XNN(); break;
	case 0x5000:
		op_5XY0(); break;
	case 0x6000:
		op_6XNN(); break;
	case 0x7000:
		op_7XNN(); break;
	case 0x8000:
		switch(opcode & 0x000F) {
		case 0x0000:
			op_8XY0(); break;
		case 0x0001:
			op_8XY1(); break;
		case 0x0002:
			op_8XY2(); break;
		case 0x0003:
			op_8XY3(); break;
		case 0x0004:
			op_8XY4(); break;
		case 0x0005:
			op_8XY5(); break;
		case 0x0006:
			op_8XY6(); break;
		case 0x0007:
			op_8XY7(); break;
		case 0x000E:
			op_8XYE(); break;
		}
		break;
	case 0x9000:
		op_9XY0(); break;
	case 0xA000:
		op_ANNN(); break;
	case 0xB000:
		op_BNNN(); break;
	case 0xC000:
		op_CXNN(); break;
	case 0xD000:
		op_DXYN(); break;
	case 0xE000:
		switch(opcode & 0x000F) {
		case 0x000E:
			op_EX9E(); break;
		case 0x0001:
			op_EXA1(); break;
		}
		break;
	case 0xF000:
		switch(opcode & 0x00FF) {
		case 0x0007:
			op_FX07(); break;
		case 0x000A:
			op_FX0A(); break;
		case 0x0015:
			op_FX15(); break;
		case 0x0018:
			op_FX18(); break;
		case 0x001E:
			op_FX1E(); break;
		case 0x0029:
			op_FX29(); break;
		case 0x0033:
			op_FX33(); break;
		case 0x0055:
			op_FX55(); break;
		case 0x0065:
			op_FX65(); break;
		}
	}
	if(delay_timer > 0) {
		--delay_timer;
	}
	if(sound_timer > 0) {
		--sound_timer;
	}
}

void Chip8::op_0NNN()
{
}

void Chip8::op_00E0()
{
	memset(gfx, 0, sizeof(gfx));
	drawFlag = true;
	pc += 2;
}

void Chip8::op_00EE()
{
	pc = stack[--sp] + 2;
}

void Chip8::op_1NNN()
{
	pc = opcode & 0x0FFF;
}

void Chip8::op_2NNN()
{
	stack[sp++] = pc;
	pc = opcode & 0x0FFF;
}

void Chip8::op_3XNN()
{
	pc += (V[(opcode & 0x0F00) >> 8] == (opcode & 0x00FF)) ? 4 : 2;
}

void Chip8::op_4XNN()
{
	pc += (V[(opcode & 0x0F00) >> 8] != (opcode & 0x00FF)) ? 4 : 2;
}

void Chip8::op_5XY0()
{
	pc += (V[(opcode & 0x0F00) >> 8] == V[(opcode & 0x00F0) >> 4]) ? 4 : 2;
}

void Chip8::op_6XNN()
{
	V[(opcode & 0x0F00) >> 8] = opcode & 0x00FF;
	pc += 2;
}

void Chip8::op_7XNN()
{
	V[(opcode & 0x0F00) >> 8] += opcode & 0x00FF;
	pc += 2;
}

void Chip8::op_8XY0()
{
	V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4];
	pc += 2;
}

void Chip8::op_8XY1()
{
	V[(opcode & 0x0F00) >> 8] |= V[(opcode & 0x00F0) >> 4];
	pc += 2;
}

void Chip8::op_8XY2()
{
	V[(opcode & 0x0F00) >> 8] &= V[(opcode & 0x00F0) >> 4];
	pc += 2;
}

void Chip8::op_8XY3()
{
	V[(opcode & 0x0F00) >> 8] ^= V[(opcode & 0x00F0) >> 4];
	pc += 2;
}

void Chip8::op_8XY4()
{
	V[0xF] = V[(opcode & 0x00F0) >> 4] > (0xFF - V[(opcode &0x0F00) >> 8]);
	V[(opcode & 0x0F00) >> 8] += V[(opcode & 0x00F0) >> 4];
	pc += 2;
}

void Chip8::op_8XY5()
{
	V[0xF] = !(V[(opcode & 0x00F0) >> 4] > V[(opcode & 0x0F00) >> 8]);
	V[(opcode & 0x0F00) >> 8] -= V[(opcode & 0x00F0) >> 4];
	pc += 2;
}

void Chip8::op_8XY6()
{
	V[0xF] = V[(opcode & 0x0F00) >> 8] & 0x1;
	V[(opcode & 0x0F00) >> 8] >>= 1;
	pc += 2;
}

void Chip8::op_8XY7()
{
	V[0xF] = !(V[(opcode & 0x0F00) >> 8] > V[(opcode & 0x00F0) >> 4]);
	V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4] - V[(opcode & 0x0F00) >> 8];
	pc += 2;
}

void Chip8::op_8XYE()
{
	V[0xF] = V[(opcode & 0x0F00) >> 8] >> 7;
	V[(opcode & 0x0F00) >> 8] <<= 1;
	pc += 2;
}

void Chip8::op_9XY0()
{
	pc += (V[(opcode & 0x0F00) >> 8] != V[(opcode & 0x00F0) >> 4]) ? 4 : 2;
}

void Chip8::op_ANNN()
{
	I = opcode & 0x0FFF;
	pc += 2;
}

void Chip8::op_BNNN()
{
	pc = (opcode & 0x0FFF) + V[0];
}

void Chip8::op_CXNN()
{
	V[(opcode & 0x0F00) >> 8] = (rand() % 0xFF) & (opcode & 0x00FF);
	pc += 2;
}

void Chip8::op_DXYN()
{
	unsigned short x = V[(opcode & 0x0F00) >> 8];
	unsigned short y = V[(opcode & 0x00F0) >> 4];
	unsigned short height = opcode & 0x000F;
	unsigned short pixel = 0;
	V[0xF] = 0;
	for(int yline = 0; yline < height; ++yline) {
		pixel = memory[I+yline];
		for(int xline = 0; xline < 8; ++xline) {
			if((pixel & (0x80 >> xline)) != 0)
			{
				if(gfx[(x + xline + ((y + yline) * 64))] == 1)
				{
					V[0xF] = 1;
				}
				gfx[x + xline + ((y + yline) * 64)] ^= 1;
			}
		}
	}
	drawFlag = true;
	pc += 2;
}

void Chip8::op_EX9E()
{
	pc += (key[V[(opcode & 0x0F00) >> 8]]) ? 4 : 2;
}

void Chip8::op_EXA1()
{
	pc += (key[V[(opcode & 0x0F00) >> 8]]) ? 2 : 4;
}

void Chip8::op_FX07()
{
	V[(opcode & 0x0F00) >> 8] = delay_timer;
	pc += 2;
}

void Chip8::op_FX0A()
{
	bool keyPress = false;

	for(int i = 0; i < 16; ++i)
	{
		if(key[i] != 0)
		{
			V[(opcode & 0x0F00) >> 8] = i;
			keyPress = true;
		}
	}

	if(!keyPress) {
		return;
	}
	pc += 2;
}

void Chip8::op_FX15()
{
	delay_timer = V[(opcode & 0x0F00) >> 8];
	pc += 2;
}

void Chip8::op_FX18()
{
	sound_timer = V[(opcode & 0x0F00) >> 8];
	pc += 2;
}

void Chip8::op_FX1E()
{
	V[0xF] = (I + V[(opcode & 0x0F00) >> 8]) > 0xFFF;
	I += V[(opcode & 0x0F00) >> 8];
	pc += 2;
}

void Chip8::op_FX29()
{
	I = V[(opcode & 0x0F00) >> 8] * 5;
	pc += 2;
}

void Chip8::op_FX33()
{
	unsigned short vx = V[(opcode & 0x0F00) >> 8];
	memory[I] = vx / 100;
	memory[I+1] = vx / 10 % 10;
	memory[I+2] = vx % 10;
	pc += 2;
}

void Chip8::op_FX55()
{
	unsigned short vx = V[(opcode & 0x0F00) >> 8];
	for(int i = 0; i <= vx; ++i) {
		memory[I+i] = V[i];
	}
	I += ((opcode & 0x0F00) >> 8) + 1;
	pc += 2;
}

void Chip8::op_FX65()
{
	unsigned short vx = V[(opcode & 0x0F00) >> 8];
	for(int i = 0; i <= vx; ++i) {
		V[i] = memory[I+i];
	}
	I += ((opcode & 0x0F00) >> 8) + 1;
	pc += 2;
}
